#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
#include <sys/socket.h>
#include <netinet/in.h>


#include <yirae/ylist.h>
#include <netutils/ac.h>
#include <netutils/sockaddr.h>

#include "configfile.h"

#define CONFIGFILE "/etc/approxyd.conf"

struct sockaddr_in *addr_rem;

void cb_sock_read(int fd, void* data)
{
	char *msg = malloc(255 * sizeof(*msg));
	memset(msg, '\0', 255);

	int *fdp = (int *)data;
	int remfd = *fdp;

	int num_bytes = recv(fd, msg, 255, 0);
	if(!num_bytes) {
		ac_callback_unregister(AC_CALLBACK_TYPE_READ, fd);
		ac_callback_unregister(AC_CALLBACK_TYPE_READ, remfd);
		close(fd);
		close(remfd);
		return;
	}



	send(remfd, msg, num_bytes, 0);

}

static void _handle_new_client(int client)
{
	syslog(LOG_DEBUG, "Connecting to remote server...");
	int rem = socket(AF_INET, SOCK_STREAM, 0);
	if(rem == -1) {
		perror("");
		return;
	}


	int c = socket_connect(rem, addr_rem);
	if(c == -1) {
		perror("");
		return;
	}
	syslog(LOG_DEBUG, "Ok\n");

	syslog(LOG_DEBUG, "Registering read callbacks for new connection...");
	ac_callback_register(AC_CALLBACK_TYPE_READ, client, cb_sock_read, &rem, sizeof(int));
	ac_callback_register(AC_CALLBACK_TYPE_READ, rem, cb_sock_read, &client, sizeof(int));
	syslog(LOG_DEBUG, "Ok\n");
}


void cb_local_accept(int fd, void *data)
{
	struct sockaddr_in *addr;
	syslog(LOG_DEBUG, " => Accepting new connection");
	int client = socket_accept(fd, addr);
	if(client == -1) {
		perror("");
	}

	syslog(LOG_DEBUG, " <= Connection Accepted");
	_handle_new_client(client);
}

int sockaddr_in_listen(struct sockaddr_in * addr, int queue)
{
	int s = socket(AF_INET, SOCK_STREAM, 0);
	if(s == -1) {
		return -1;
	}

	int yes = 1;

	if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
		return -1;
	}

	int c;
	c = socket_bind(s, addr);
	if(c == -1) {
		return -1;
	}



	listen(s, queue);

	return s;
}

int main()
{
	openlog("approxyd", LOG_PID, LOG_DAEMON);
		
	int i;
	ConfigFile cf = configfile_new(CONFIGFILE);
	if(!cf) {
		syslog(LOG_CRIT, "Cannot open config file\n");
		fprintf(stderr, "Cannot open config file\n");

		configfile_free(cf);
		closelog();

		exit(EXIT_FAILURE);
	}

	syslog(LOG_DEBUG, "Config file read");


	char* ip_remote = configfile_get_ip_remote(cf);
	char* ip_local = configfile_get_ip_local(cf);
	int port_remote = configfile_get_port(cf);

	addr_rem = sockaddr_in_new(ip_remote, port_remote);

	yList ports_local = configfile_get_ports(cf);
	yList srv_socks = ylist_new();
	
	struct sockaddr_in *addr_srv;
	int p;
	int sd;

	for(i = 0; i < ylist_length(ports_local); i++) {

		 p = ylist_int_get(ports_local, i);
		addr_srv = sockaddr_in_new(ip_local, p);
		assert(addr_srv);

		sd = sockaddr_in_listen(addr_srv, 5);
		if(sd == -1) {
			perror("approxyd:");
			exit(EXIT_FAILURE);
		}

		ylist_int_append(srv_socks, sd);
	}

	int srv;

	if(ac_init()) {

		for(i = 0; i < ylist_length(srv_socks); i++) {
			srv = ylist_int_get(srv_socks, i);
			ac_callback_register(AC_CALLBACK_TYPE_READ, srv, cb_local_accept, NULL, 0);
			syslog(LOG_DEBUG, "Registered socket descriptor : %d", srv);
		}

		ac_mainloop_daemonize();

	} else {
		syslog(LOG_CRIT, "Error initializing asynchore!\n");
		fprintf(stderr, "Error initializing asynchore!\n");
	}

	ylist_shallow_free(&srv_socks);
	ylist_shallow_free(&ports_local);
	configfile_free(cf);

	syslog(LOG_DEBUG, "Exiting Cleanly");
	closelog();
	exit(EXIT_SUCCESS);
}
